type op = Add | Sub | Eq | Neq | Lt | Gt | Leq | Geq | And | Or | Vat | Len | In | Out | Not | Rng | Sai
type typ = Int | Arr | Str | Any

type term =
	Integer of int
	| Id of string
	| String of string
	| Array of term list
	| Null

type var = {
	varType : typ;
	varName : string;
	dType  : typ;
}

type expr =
	Term of term
	| Call of string * expr list
	| Range of term * term * term
	| Binop of expr * op * term
	| Unop of expr * op
	
type stmt = 
	Block of stmt list
	| Return of expr
	| If of expr * stmt
	| Foreach of string * term * stmt
	| While of expr * stmt
	| Output of term * expr
	| Assign of var * expr
	| SetAIndex of term * term * expr

type func_decl = {
    fname : string;
    params : var list;
    locals : var list;
    body : stmt list;
  }

type program = string list * func_decl list


(* pretty print the ast *)

let string_of_var v = 
	(match v.varType with
  	Int -> "int "
  	| Str -> "str "
  	| Arr -> "str[] "
  	| Any -> "") ^ v.varName

let rec string_of_term = function
    Integer(i) -> string_of_int i
  | String(s) -> "\"" ^ s ^ "\""
  | Id(s) -> s
  | Array(el) ->
      "[" ^ String.concat ", " (List.map string_of_term el) ^ "]"
  | Null -> ""

let rec string_of_expr = function
   Term(a) -> string_of_term a
  | Call(f, el) -> f ^ "(" ^ String.concat ", " (List.map string_of_expr el) ^ ")"
  | Range(id, a1, a2) -> string_of_term id ^ "[" ^ string_of_term a1 ^ ":" ^ string_of_term a2 ^ "]"
  | Binop(e1, o, a) -> (match o with
	Add -> string_of_expr e1 ^ " + " ^ string_of_term a
	| Sub -> string_of_expr e1 ^ " - " ^ string_of_term a
     | Eq -> string_of_expr e1 ^ " == " ^ string_of_term a
     | Neq -> string_of_expr e1 ^ " != " ^ string_of_term a
     | Lt -> string_of_expr e1 ^ " < " ^ string_of_term a 
     | Leq -> string_of_expr e1 ^ " <= " ^ string_of_term a 
     | Gt -> string_of_expr e1 ^ " > " ^ string_of_term a 
     | Geq -> string_of_expr e1 ^ " >= " ^ string_of_term a
     | And -> string_of_expr e1 ^ " && " ^ string_of_term a 
     | Or -> string_of_expr e1 ^ " || " ^ string_of_term a
     | Vat -> string_of_expr e1 ^ "[" ^ string_of_term a ^ "]"
     | _ -> "") (* will never happen, but satisfies compiler *)
  | Unop(v, o) -> (match o with
  	Len -> "|" ^ string_of_expr v ^ "|"
	| Not -> "!" ^ string_of_expr v
	| In -> "@" ^ string_of_expr v
     | _ -> "") (* will never happen, but satisfies compiler *)

let rec string_of_stmt = function
    Block(stmts) -> print_endline "block";
      "\n\t" ^ String.concat "\t" (List.map string_of_stmt stmts) ^ "\n"
  | Return(expr) -> "RETURN " ^ string_of_expr expr ^ "\n";
  | If(e, s) -> "IF (" ^ string_of_expr e ^ ")\n" ^ string_of_stmt s
  | Foreach(id, a, s) ->
      "FOREACH (" ^ id ^ " IN " ^ string_of_term a ^ ") " ^ string_of_stmt s
  | While(e, s) -> "WHILE (" ^ string_of_expr e ^ ") " ^ string_of_stmt s
  | Output(dest, src) -> "@" ^ string_of_term dest ^ " = " ^ string_of_expr src 
  | Assign(v, e) -> string_of_var v ^ " = " ^ string_of_expr e
  | SetAIndex(id, i, e) -> string_of_term id ^ "[" ^ string_of_term i ^ "] = " ^ string_of_expr e

let string_of_fdecl fdecl =
  fdecl.fname ^ "(" ^ String.concat ", " (List.map string_of_var fdecl.params) ^ ")\n\t" ^
 "Locals: " ^ String.concat ", " (List.map string_of_var (fdecl.locals)) ^ "\n\t" ^
  String.concat "\n\t" (List.map string_of_stmt fdecl.body) ^ "\n"

let string_of_program program = 
  String.concat "\n" (List.map string_of_fdecl (List.rev (snd program)))

